/**
 * 
 */
package cz.cvut.fit.bugtrac.gexf;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.ojn.gexf4j.core.Gexf;
import com.ojn.gexf4j.core.Node;
import com.ojn.gexf4j.core.impl.GexfImpl;
import com.ojn.gexf4j.core.impl.StaxGraphWriter;

import cz.cvut.fit.bugtrac.dao.GraphDAO;
import cz.cvut.fit.bugtrac.exception.BugTracException;
import cz.cvut.fit.bugtrac.persistence.Edge;
import cz.cvut.fit.bugtrac.persistence.Graph;

/**
 * @author Jarec
 * 
 */
@Component
public class GexfServiceImpl implements GexfService {

	private static transient final Logger log = Logger
			.getLogger(GexfServiceImpl.class.getName());

	private GraphDAO graphDAO;

	/*
	 * (non-Javadoc)
	 * 
	 * @see cz.cvut.fit.bugtrac.gexf.GexfService#getGexfGraph()
	 */
	@Override
	@Transactional(propagation = Propagation.REQUIRED, readOnly = true)
	public String getGexfGraph() throws BugTracException, IOException {

		Gexf gexf = new GexfImpl();

		Collection<Graph> rawGraphs = graphDAO.getSnapshots();

		List<Date> dates = new ArrayList<Date>();
		Map<MyNode, List<Date>> nodes = new HashMap<MyNode, List<Date>>();
		Map<MyEdge, List<Date>> edges = new HashMap<MyEdge, List<Date>>();

		for (Graph rawGraph : rawGraphs) {
			Date createdDate = rawGraph.getCreated();
			dates.add(createdDate);

			for (Edge edge : rawGraph.getEdges()) {
				if (!edge.getAssigner().trim()
						.equalsIgnoreCase(edge.getReporter().trim())) {
					MyNode first = new MyNode(edge.getAssigner());
					if (nodes.containsKey(first)) {
						if (nodes.get(first).size() == 2) {
							nodes.get(first).remove(1);
						}
						nodes.get(first).add(createdDate);
					} else {
						List<Date> date = new ArrayList<Date>();
						date.add(createdDate);
						date.add(createdDate);
						nodes.put(first, date);
						date = null;
					}

					MyNode second = new MyNode(edge.getReporter());
					if (nodes.containsKey(second)) {
						if (nodes.get(second).size() == 2) {
							nodes.get(second).remove(1);
						}
						nodes.get(second).add(createdDate);
					} else {
						List<Date> date = new ArrayList<Date>();
						date.add(createdDate);
						date.add(createdDate);
						nodes.put(second, date);
						date = null;
					}

					if (!first.equals(second)) {
						MyEdge myEdge = new MyEdge(edge.getName(), first,
								second);
						if (edges.containsKey(myEdge)) {
							if (edges.get(myEdge).size() == 2) {
								edges.get(myEdge).remove(1);
							}
							edges.get(myEdge).add(createdDate);
						} else {
							List<Date> date = new ArrayList<Date>();
							date.add(createdDate);
							date.add(createdDate);
							edges.put(myEdge, date);
							date = null;
						}
					}

				}
			}
		}

		Date dateMax = Collections.max(dates);

		Map<MyNode, Node> myNodeToNode = new HashMap<MyNode, Node>();

		for (MyNode myNode : nodes.keySet()) {
			log.fine("Creating node for: " + myNode.getName());
			Node node = gexf.getGraph().createNode(myNode.getName());
			node.setLabel(myNode.getName());

			List<Date> myDates = nodes.get(myNode);
			if (myDates.size() > 0) {
				node.setStartDate(myDates.get(0));
			}
			if (myDates.size() > 1 && !myDates.get(1).equals(dateMax)) {
				if (!node.hasEndDate()
						|| node.getEndDate().before(myDates.get(1))) {
					node.setEndDate(myDates.get(1));
				}
			}

			myNodeToNode.put(myNode, node);

		}

		for (Node node : gexf.getGraph().getNodes()) {
			if (node.hasEndDate() && node.getEndDate().equals(dateMax)) {
				node.clearEndDate();
			}
		}

		for (MyEdge myEdge : edges.keySet()) {
			log.fine("Creating edge for: " + myEdge.getId() + " set: "
					+ myEdge.getNodes());
			Iterator<MyNode> it = myEdge.getNodes().iterator();
			Node source = myNodeToNode.get(it.next());
			Node target = myNodeToNode.get(it.next());

			List<Date> myDates = edges.get(myEdge);

			com.ojn.gexf4j.core.Edge edge = source.connectTo(myEdge.getId(),
					target);
			if (myDates.size() > 0) {
				if (!edge.hasStartDate()
						|| edge.getStartDate().after(myDates.get(0))) {
					edge.setStartDate(myDates.get(0));
				}
			}
			if (myDates.size() > 1 && !myDates.get(1).equals(dateMax)) {
				if (!edge.hasEndDate()
						|| edge.getEndDate().before(myDates.get(1))) {
					edge.setEndDate(myDates.get(1));
				}
			}

		}

		for (com.ojn.gexf4j.core.Edge edge : gexf.getGraph().getAllEdges()) {
			if (edge.hasEndDate() && edge.getEndDate().equals(dateMax)) {
				edge.clearEndDate();
			}
		}

		StaxGraphWriter writer = new StaxGraphWriter();

		ByteArrayOutputStream baos = new ByteArrayOutputStream();

		writer.writeToStream(gexf, baos);

		return baos.toString("UTF8");
	}

	/**
	 * @return the graphDAO
	 */
	public GraphDAO getGraphDAO() {
		return graphDAO;
	}

	/**
	 * @param graphDAO
	 *            the graphDAO to set
	 */
	@Autowired
	public void setGraphDAO(GraphDAO graphDAO) {
		this.graphDAO = graphDAO;
	}

}
